﻿using System;
using System.Collections.Generic;
using System.Text;
using ITAP.Core.Modelling.Primitives;
using ITAP.ShprossesBuilder.Calculations;
using System.Diagnostics;
using ITAP.Core.Calculations;
using ITAP.Core.Modelling;

namespace ITAP.ShprossesBuilder.Modelling.Components.Constraints
{
    public enum ConnectionType
    {
        EndToEnd,
        Sticked
    }

    [Serializable]
    /// <summary>
    /// Поправляет улы резов на концах IEndedComponent компонентов, 
    /// которые приконнекчены к компоненту типа IEndedComponent. Используется для корректировки 
    /// углов реза между соединённого с данным компонентом концами компонента, 
    /// или компонентов с концами, которые приконнекчены "сбоку" компонента с концами. Тип действия для данного 
    /// типа приконнекченного компонента определяется в конструкторе специальным перечислением. 
    /// </summary>
    public class EndedComponentConnectedComponentsCutAngleConstraint : IConstraint
    {
        private bool disableAfterFirstApply;
        private bool enabled = true;
        private Type typeOfConnectedComponents;
        private ConnectionType cutAction;

        public EndedComponentConnectedComponentsCutAngleConstraint(Type typeOfConnectedComponents, ConnectionType cutAction, bool disableAfterFirstApply)
        {
            this.disableAfterFirstApply = disableAfterFirstApply;
            this.typeOfConnectedComponents = typeOfConnectedComponents;
            this.cutAction = cutAction;
        }

        #region IConstraint Members

        public bool Check(IComponent component)
        {
            // TODO Проще было сразу возвращать "ложь", чтобы фиксировать углы каждый раз, чем проверять перед фиксацией их правильность.
            return false;
        }

        public void Fix(IComponent component)
        {
            if (!enabled) return;

            if (!(component is IEndedComponent))
                return;
            foreach (IComponent connectedComponent in component.ConnectedComponents)
            {
                if (this.typeOfConnectedComponents != connectedComponent.GetType()) continue;
                if (!(connectedComponent is IEndedComponent)) continue;


                IEndedComponent endedComponent = component as IEndedComponent;
                IEndedComponent endedConnectedComponent = connectedComponent as IEndedComponent;
                IWidePrimitive componentPrimitive = (IWidePrimitive)endedComponent.Primitives[0];
                IWidePrimitive connectedComponentPrimitive = (IWidePrimitive)endedConnectedComponent.Primitives[0];

                if (cutAction == ConnectionType.EndToEnd)
                {
                    // Ищем соединения КОНЦОВ компонента с КОНЦАМИ приконнекченного компонента, если нашли, 
                    // то нужно исправлять их, если нет, значит, у них нет соединения "конец в конец", и нужно 
                    // пропускать приконнекченный компонент и идти к следующему.
                    EndType[] endTypes = this.GetConnectedEndTypesOfComponents(endedComponent, endedConnectedComponent);
                    if (endTypes[0] != EndType.NotDefined && endTypes[1] != EndType.NotDefined)
                    {
                        // points[0] или points[3] - точка пересечения центральных линий примитивов, если componentPrimitive - это 
                        // LinePrimitive, points[0] - если componentPrimitive - это CirclePrimitive, к началу 
                        // присоединён connectedComponentPrimitive, и points[3], если это CirclePrimitive, 
                        // к концу которого присоединён connectedComponentPrimitive.
                        // Теперь ищем точки пересечения нижних и верхних границ.
                        //
                        // Точки для исправления концов границ приконнекченного примитива.
                        //ModelPoint[] downPoint;
                        //ModelPoint[] upPoint;
                        if (endTypes[0] != endTypes[1])
                        {
                            //downPoint = connectedComponentPrimitive.DownBorder.Intersect(componentPrimitive.DownBorder);
                            //upPoint = connectedComponentPrimitive.UpBorder.Intersect(componentPrimitive.UpBorder);
                            if (endTypes[1] == EndType.Start)
                            {
                                ModelPoint[] points = componentPrimitive.Intersect(connectedComponentPrimitive, EndType.End);

                                // Пока что ограничение считает, что если соединение "конец в конец", то 
                                // всегда конец компонента соединяется с началом следующего компонента. 
                                // Поэтому для приконнекченного примитива сейчас берём новую центральную точку, 
                                // ближайшую к концу примитива.
                                // Раньше было так: endedConnectedComponent.StartPoint = (ModelPoint)points[0].Clone();
                                if (!ModelCalculations.IsErrorPoint(points[0]))
                                    endedConnectedComponent.StartPoint = (ModelPoint)points[0].Clone();
                                if (!ModelCalculations.IsErrorPoint(points[4]))
                                {
                                    connectedComponentPrimitive.DownBorder.StartPoint = (ModelPoint)points[4].Clone();
                                    connectedComponentPrimitive.Update();
                                }
                                if (!ModelCalculations.IsErrorPoint(points[8]))
                                {
                                    connectedComponentPrimitive.UpBorder.StartPoint = (ModelPoint)points[8].Clone();
                                    connectedComponentPrimitive.Update();
                                }

                            }
                            else if (endTypes[1] == EndType.End)
                            {
                                ModelPoint[] points = componentPrimitive.Intersect(connectedComponentPrimitive, EndType.Start);

                                if (!ModelCalculations.IsErrorPoint(points[0]))
                                    endedConnectedComponent.EndPoint = (ModelPoint)points[0].Clone();

                                if (!ModelCalculations.IsErrorPoint(points[4]))
                                {
                                    connectedComponentPrimitive.DownBorder.EndPoint = (ModelPoint)points[4].Clone();
                                    connectedComponentPrimitive.Update();
                                }

                                if (!ModelCalculations.IsErrorPoint(points[8]))
                                {
                                    connectedComponentPrimitive.UpBorder.EndPoint = (ModelPoint)points[8].Clone();
                                    connectedComponentPrimitive.Update();
                                }
                            }
                            else
                                throw new Exception();


                        }
                        else if (endTypes[0] == EndType.Both && endTypes[1] == EndType.Both)
                        {
                            ModelPoint[] points = componentPrimitive.Intersect(connectedComponentPrimitive, EndType.End);
                            if (!ModelCalculations.IsErrorPoint(points[0]))
                                endedConnectedComponent.StartPoint = (ModelPoint)points[0].Clone();
                            if (!ModelCalculations.IsErrorPoint(points[4]))
                            {
                                connectedComponentPrimitive.DownBorder.StartPoint = (ModelPoint)points[4].Clone();
                                connectedComponentPrimitive.Update();
                            }
                            if (!ModelCalculations.IsErrorPoint(points[8]))
                            {
                                connectedComponentPrimitive.UpBorder.StartPoint = (ModelPoint)points[8].Clone();
                                connectedComponentPrimitive.Update();
                            }
                            points = componentPrimitive.Intersect(connectedComponentPrimitive, EndType.Start);

                            if (!ModelCalculations.IsErrorPoint(points[0]))
                                endedConnectedComponent.EndPoint = (ModelPoint)points[0].Clone();

                            if (!ModelCalculations.IsErrorPoint(points[4]))
                            {
                                connectedComponentPrimitive.DownBorder.EndPoint = (ModelPoint)points[4].Clone();
                                connectedComponentPrimitive.Update();
                            }

                            if (!ModelCalculations.IsErrorPoint(points[8]))
                            {
                                connectedComponentPrimitive.UpBorder.EndPoint = (ModelPoint)points[8].Clone();
                                connectedComponentPrimitive.Update();
                            }
                        }

                        // TODO Пока поставил заглушку для того, чтобы сделать углы реза только в дистанционной рамке (там всегда конец с началом соединён), потом доделать!!
                        else throw new Exception();


                    }
                    // Если соединение не "конец в конец", переходим к следующем приконнекченному компоненту.
                    else
                        continue;
                }
                else
                {
                    // Надо найти ближайшую границу компонента и посчитать угол среза относительно неё, чтобы 
                    // получить эффект "приклеенного сбоку" приконнекченного компонента.

                    // Пока что реализую поправку углов реза только для начала приконнекченного компонента, 
                    // для случая, если компонент приконнекчен концом, написать позже (т.к. сейчас реализую только 
                    // для случая приконнекченного компонента типа SpacerConnector и Connector, которые коннектятся 
                    // "сбоку" компонента только началом.

                    // Если приконнекченный компонент соединён с компонентом началом
                    if (endedConnectedComponent.StartConnectedComponent == endedComponent)
                    {
                        //double distanceToDownBorder = componentPrimitive.DownBorder.GetDistanceToPoint(connectedComponentPrimitive.StartPoint); // ModelCalculations.GetDistance(connectedComponentPrimitive.StartPoint, componentPrimitive.DownBorder);
                        //double distanceToUpBorder = componentPrimitive.UpBorder.GetDistanceToPoint(connectedComponentPrimitive.StartPoint); // ModelCalculations.GetDistance(connectedComponentPrimitive.StartPoint, componentPrimitive.UpBorder);

                        ModelPoint[] points;

                        points = connectedComponentPrimitive.Intersect(componentPrimitive, EndType.Start);

                        //// Если начало ближе к нижней грани компонента
                        //if (distanceToDownBorder < distanceToUpBorder)
                        //{
                        //    connectedComponentPrimitive.StartPoint = points[1];
                        //    connectedComponentPrimitive.DownBorder.StartPoint = (ModelPoint)points[4].Clone();
                        //    connectedComponentPrimitive.Update();
                        //    connectedComponentPrimitive.UpBorder.StartPoint = (ModelPoint)points[7].Clone();
                        //    connectedComponentPrimitive.Update();                            
                        //}
                        //// Если начало ближе к верхней грани компонента
                        //else if (distanceToUpBorder < distanceToDownBorder)
                        {
                            // TODO Иногда длина отрезка NaN, найти багу
                            if (double.IsNaN(connectedComponentPrimitive.Length))
                                Debugger.Break();

                            ModelPoint[] _points = new ModelPoint[3];

                            StickConnectionType stickType = endedConnectedComponent.AcceptedComponentTypesToConnect.GetStickType(endedComponent.GetType());
                            StickConnectionType sticked;
                            ModelPoint nearestPoint = component.GetNearestPointOnComponent(endedConnectedComponent.StartPoint, endedConnectedComponent.EndPoint, StickConnectionType.ToNearestBorder, out sticked, ProjectionType.Intersection);

                            switch (stickType)
                            {
                                case StickConnectionType.ToNearestBorder:
                                    if (nearestPoint.Equals(points[1]))
                                    {
                                        _points[0] = points[1];
                                        _points[1] = points[4];
                                        _points[2] = points[7];
                                    }
                                    else if (nearestPoint.Equals(points[2]))
                                    {
                                        _points[0] = points[2];
                                        _points[1] = points[5];
                                        _points[2] = points[8];
                                    }
                                    else
                                        continue;
                                    break;
                                default:
                                    break;
                            }

                            if (SimpleEqualProvider.IsEqual(connectedComponentPrimitive.Length, 0, 0.00001))
                            {
                                connectedComponentPrimitive.EndPoint = (ModelPoint)_points[0].Clone();
                                connectedComponentPrimitive.DownBorder.EndPoint = (ModelPoint)_points[1].Clone();
                                connectedComponentPrimitive.Update();
                                connectedComponentPrimitive.UpBorder.EndPoint = (ModelPoint)_points[2].Clone();
                                connectedComponentPrimitive.Update();
                            }

                            connectedComponentPrimitive.StartPoint = (ModelPoint)_points[0].Clone();
                            connectedComponentPrimitive.DownBorder.StartPoint = (ModelPoint)_points[1].Clone();
                            connectedComponentPrimitive.Update();
                            connectedComponentPrimitive.UpBorder.StartPoint = (ModelPoint)_points[2].Clone();
                            connectedComponentPrimitive.Update();

                        }
                        //else
                        //    throw new Exception();

                    }
                    // Если приконнекченный компонент соединён с компонентом концом
                    else if (endedConnectedComponent.EndConnectedComponent == endedComponent)
                    {
                        continue;
                        //throw new Exception();
                    }
                    else
                        throw new Exception();

                }
            }
            if (disableAfterFirstApply)
                this.enabled = false;
        }

        public bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
            }
        }

        public ConstraintAction Action
        {
            get { return ConstraintAction.Fix; }
        }


        #endregion

        /// <summary>
        /// Первый элемент массива - тип конца endedComponent, второй элемент массива - тип конца endedConnectedComponent.
        /// </summary>
        /// <param name="endedComponent"></param>
        /// <param name="endedConnectedComponent"></param>
        /// <returns></returns>
        private EndType[] GetConnectedEndTypesOfComponents(IEndedComponent endedComponent, IEndedComponent endedConnectedComponent)
        {
            if (endedComponent.StartConnectedComponent == (IComponent)endedConnectedComponent)
            {
                if (endedConnectedComponent.EndConnectedComponent == (IComponent)endedComponent)
                {
                    if (endedConnectedComponent.StartConnectedComponent == (IComponent)endedComponent)
                        return new EndType[] { EndType.Both, EndType.Both };
                    else
                        return new EndType[] { EndType.Start, EndType.End };
                }

                else if (endedConnectedComponent.StartConnectedComponent == (IComponent)endedComponent)
                    return new EndType[] { EndType.Start, EndType.Start };
                else
                    return new EndType[] { EndType.Start, EndType.NotDefined };

            }
            else if (endedComponent.EndConnectedComponent == (IComponent)endedConnectedComponent)
            {
                if (endedConnectedComponent.StartConnectedComponent == (IComponent)endedComponent)
                {
                    if (endedConnectedComponent.EndConnectedComponent == (IComponent)endedComponent)
                    {
                        return new EndType[] { EndType.Both, EndType.Both };
                    }
                    else
                        return new EndType[] { EndType.End, EndType.Start };
                }
                else if (endedConnectedComponent.EndConnectedComponent == (IComponent)endedComponent)
                    return new EndType[] { EndType.End, EndType.End };
                else
                    return new EndType[] { EndType.End, EndType.NotDefined };
            }
            else
            {
                if (endedConnectedComponent.StartConnectedComponent == (IComponent)endedComponent)
                    return new EndType[] { EndType.NotDefined, EndType.Start };
                else if (endedConnectedComponent.EndConnectedComponent == (IComponent)endedComponent)
                    return new EndType[] { EndType.NotDefined, EndType.End };
                else
                    return new EndType[] { EndType.NotDefined, EndType.NotDefined };
            }
        }

        #region ICloneable Members

        public object Clone()
        {
            EndedComponentConnectedComponentsCutAngleConstraint clone = new EndedComponentConnectedComponentsCutAngleConstraint(typeOfConnectedComponents, cutAction, this.disableAfterFirstApply);
            return clone;
        }

        #endregion
    }
}
